﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Xml.Linq;
using System.Security.Cryptography;

namespace MisprintTestingTool
{
    public partial class MainForm : Form
    {
        string inpFilePath = "";
        Encoding codefile = Encoding.GetEncoding("windows-1251");

        static string strDate; // date для xml
        static string strUid; // uid для xml

        static List<Misprint> _misprints = new List<Misprint>();


        // XML sections
        static XElement userSection;
        static XElement misprintsSection;

        class MyXml
        {
            public XElement CreateUserSection
            {
                get
                {
                    if (Program.userInfo.isStudent) // student
                    {
                        XElement userSection =
                            new XElement("UserSection",
                                new XAttribute("role", Program.userInfo.role),
                                new XElement("SecondName", Program.userInfo.surname),
                                new XElement("FirstName", Program.userInfo.name),
                                new XElement("MiddleName", Program.userInfo.middleName),
                                new XElement("ExperienceSkillsPC", Program.userInfo.experienceSkillsPC),

                                new XElement("Faculty", Program.userInfo.faculty),
                                new XElement("Specialty", Program.userInfo.specialty),
                                new XElement("Course", Program.userInfo.course)
                            );

                        return userSection;
                    }
                    else // worker
                    {
                        XElement userSection =
                            new XElement("UserSection",
                                new XAttribute("role", Program.userInfo.role),
                                new XElement("SecondName", Program.userInfo.surname),
                                new XElement("FirstName", Program.userInfo.name),
                                new XElement("MiddleName", Program.userInfo.middleName),
                                new XElement("ExperienceSkillsPC", Program.userInfo.experienceSkillsPC),

                                new XElement("Job", Program.userInfo.job),
                                new XElement("SeniorityWork", Program.userInfo.seniorityWork)
                            );

                        return userSection;
                    }
                }
            }

            public XElement CreateMisprintsSection
            {
                get
                {
                    XElement misprintsSection = new XElement("MisprintsSection");

                    foreach (var misprint in _misprints)
                    {
                        XElement elem = new XElement("Misprint", new XAttribute("type", misprint.Type));

                        foreach (var symbol in misprint.Symbols)
                        {
                            elem.Add(new XElement("Letter", TranslateSymbol(symbol.Symbol)));
                        }

                        foreach (var attr in misprint.Attributes)
                        {
                            elem.Add(new XAttribute(attr.Key, attr.Value));
                        }

                        misprintsSection.Add(elem);
                    }

                    return misprintsSection;
                }
            }

            private string TranslateSymbol(char ch)
            {
                switch (ch)
                {
                    case ' ':
                        return "Пробел";
                    case '\r':
                        return "\\r";
                    case '\n':
                        return "\\n";
                    case '\t':
                        return "\\t";
                }

                return ch.ToString();
            }

            public XElement CreateReportXml
            {
                get
                {
                    // Create report xml
                    XElement reportXml =
                        new XElement("ReportEnvelope",
                            new XElement("MisprintTestingToolReport",
                                new XAttribute("date", strDate),
                                new XAttribute("uid", strUid),
                                userSection,
                                misprintsSection
                            )
                        );

                    return reportXml;
                }
            }
        }

        class MyMD5
        {
            // Hash an input string and return the hash as
            // a 32 character hexadecimal string.
            public string getMd5Hash(string input)
            {
                // Create a new instance of the MD5CryptoServiceProvider object.
                MD5 md5Hasher = MD5.Create();

                // Convert the input string to a byte array and compute the hash.
                byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input));

                // Create a new Stringbuilder to collect the bytes
                // and create a string.
                StringBuilder sBuilder = new StringBuilder();

                // Loop through each byte of the hashed data 
                // and format each one as a hexadecimal string.
                for (int i = 0; i < data.Length; i++)
                {
                    sBuilder.Append(data[i].ToString("x2"));
                }

                // Return the hexadecimal string.
                return sBuilder.ToString();
            }
        }

        private void WriteReportToFile()
        {
            try
            {
                MyXml myXml = new MyXml();
                userSection = myXml.CreateUserSection;
                misprintsSection = myXml.CreateMisprintsSection;


                MyMD5 myMd5 = new MyMD5();
                string userSectionHash = myMd5.getMd5Hash(userSection.ToString());
                string misprintsSectionHash = myMd5.getMd5Hash(misprintsSection.ToString());


                DateTime dateValue = DateTime.Now;
                strDate = dateValue.ToString("yyyy'/'M'/'d HH:mm:ss");
                string strDataTmp = dateValue.ToString("yyyy'-'M'-'d'-'HH'-'mm'-'ss");
                strUid = strDataTmp + "-" + userSectionHash + "-" + misprintsSectionHash;


                XElement reportXml = myXml.CreateReportXml;


                string pathLog = Program.pathLog + strUid + ".xml";
                // true - добавлять данные в конец файла. false - перезаписать файл заново
                using (System.IO.StreamWriter file = new System.IO.StreamWriter(pathLog, false, Encoding.UTF8))
                {
                    file.WriteLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
                    file.WriteLine(reportXml.ToString());

                }
            }
            catch
            {
                MessageBox.Show("Ошибка при сохранении отчета", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        public MainForm()
        {
            InitializeComponent();
        }

        private void изФайлаToolStripMenuItem_Click(object sender, EventArgs e)
        {
            try
            {
                openFileDialog.FileName = "";
                openFileDialog.Filter = "Txt files (*.txt)|*.txt|All files (*.*)|*.*";
                openFileDialog.FilterIndex = 1;

                if (openFileDialog.ShowDialog() == DialogResult.OK)
                {
                    string fileFath = openFileDialog.FileName;
                    inpFilePath = fileFath;
                    StreamReader reader = new StreamReader(inpFilePath, codefile);
                    textBox2.Text = reader.ReadToEnd().Trim();
                    reader.Close();
                    _misprints.Clear();
                    //_lastText = "  ";
                    textBox1.Text = "";
                }
            }
            catch
            {
                MessageBox.Show("Ошибка при загрузке файла", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void поУмолчаниюToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.textBox2.Text = "Самое главное в обучении слепому методу - запомнить расположение клавиш. При обучении лучше использовать клавиатуру без обозначений на клавишах и при этом иметь в поле зрения (например на экране монитора) хорошо видимое изображение клавиатуры. Таким образом вы учитесь чувствовать клавиши и отвыкаете смотреть на клавиатуру.\r\n\r\nТехника слепой печати заключается в том, что вся клавиатура разделена на зоны, каждая из которых прикреплена к определенному пальцу. Клавиши из зоны для указательного пальца должны нажиматься только указательным пальцем и так далее. Процесс обучения сводится к тренировке мышечной памяти пальцев.\r\n\r\nВ начальной позиции пальцы должны находится над основным рядом клавиш ФЫВА ОЛДЖ. При наборе старайтесь не смотреть на клавиатуру, а нащупывать клавиши пальцами, ориентируясь на экранную клавиатуру.";
        }

        private void оПрограммеToolStripMenuItem_Click(object sender, EventArgs e)
        {
            (new AboutProgramForm()).ShowDialog();
        }

        private void btnCompleteTest_Click(object sender, EventArgs e)
        {
            // сохранение информации о прохождении теста
            // + информация о тестируемом
            // создание *.xml
            // определиться с местом хранения отчета

            try
            {
                string[] dest = textBox2.Text.Split(' ');
                string[] source = textBox1.Text.Split(' ');

                int destIndex = 0;
                for (int i = 0; i < source.Length && destIndex < dest.Length; )
                {
                    if (source[i].Length < dest[destIndex].Length)
                    {
                        string text = source[i];
                        var changesTmp = new List<LevenshteinMisprint>();
                        var changesText = new List<LevenshteinMisprint>();
                        int distance = 0, oldDistance = -1;
                        for (; ; )
                        {
                            changesText = new List<LevenshteinMisprint>(changesTmp);
                            changesTmp.Clear();
                            distance = LevenshteinDistance.Compute(" " + text + " ", " " + dest[destIndex] + " ", out changesTmp);

                            if (oldDistance <= distance && oldDistance != -1)
                                break;

                            oldDistance = distance;

                            i++;
                            if (i < source.Length)
                                text += " " + source[i];
                        }

                        destIndex++;

                        foreach (var ch in changesText)
                        {
                            var misprint = new Misprint()
                            {
                                Type = MisprintToString(ch.Type),
                                Symbols = new List<MisprintSymbol>(ch.MisprintChars.ConvertAll<MisprintSymbol>(a => new MisprintSymbol() { Symbol = a.Char, Position = a.Position }))
                            };

                            _misprints.Add(misprint);
                        }
                    }
                    else
                    {
                        string text = dest[destIndex];
                        var changesTmp = new List<LevenshteinMisprint>();
                        var changesText = new List<LevenshteinMisprint>();
                        int distance = 0, oldDistance = -1;
                        for (; ; )
                        {
                            changesText = new List<LevenshteinMisprint>(changesTmp);
                            changesTmp.Clear();
                            distance = LevenshteinDistance.Compute(" " + source[i] + " ", " " + text + " ", out changesTmp);

                            if (oldDistance <= distance && oldDistance != -1)
                                break;

                            oldDistance = distance;

                            destIndex++;
                            if (destIndex < dest.Length)
                                text += " " + dest[destIndex];
                        }

                        i++;

                        foreach (var ch in changesText)
                        {
                            var misprint = new Misprint()
                            {
                                Type = MisprintToString(ch.Type),
                                Symbols = new List<MisprintSymbol>(ch.MisprintChars.ConvertAll<MisprintSymbol>(a => new MisprintSymbol() { Symbol = a.Char, Position = a.Position }))
                            };

                            _misprints.Add(misprint);
                        }
                    }

                }
            }
            catch { }

            try
            {
                for (int i = 0; i < _misprints.Count; i++)
                {
                    if (_misprints[i].Type == MisprintToString(LevenshteinMisprintType.Substitute) &&
                        _misprints[i].Symbols[0].Symbol != _misprints[i].Symbols[1].Symbol &&
                        _misprints[i].Symbols[0].Symbol.ToString().ToUpper() == _misprints[i].Symbols[1].Symbol.ToString().ToUpper())
                    {
                        _misprints[i].Type = "case-invariant";
                        _misprints[i].Symbols.RemoveAt(1);
                    }

                    if (i < _misprints.Count - 1 &&
                        _misprints[i].Type == MisprintToString(LevenshteinMisprintType.Substitute) &&
                        _misprints[i + 1].Type == MisprintToString(LevenshteinMisprintType.Substitute) &&
                        _misprints[i].Symbols[0].Symbol == _misprints[i + 1].Symbols[1].Symbol &&
                        _misprints[i].Symbols[1].Symbol == _misprints[i + 1].Symbols[0].Symbol)
                    {
                        _misprints[i].Type = "incorrect-order";
                        _misprints.RemoveAt(i + 1);
                    }

                    if (_misprints[i].Type == MisprintToString(LevenshteinMisprintType.Substitute) ||
                        _misprints[i].Type == "incorrect-order")
                    {
                        _misprints[i].Attributes = new Dictionary<string, string>()
                    { 
                        { "distance", KeyDistance.Compute(_misprints[i].Symbols[0].Symbol, _misprints[i].Symbols[1].Symbol).ToString() } 
                    };
                    }

                    if ((_misprints[i].Type == MisprintToString(LevenshteinMisprintType.Substitute) ||
                        _misprints[i].Type == "incorrect-order") &&
                        _misprints[i].Symbols[0].Symbol == _misprints[i].Symbols[1].Symbol)
                    {
                        _misprints.RemoveAt(i);
                        i--;
                    }
                }
            } catch {}

            WriteReportToFile();

            MessageBox.Show("Спасибо за прохождение теста", "Вы успешно прошли тест", MessageBoxButtons.OK, MessageBoxIcon.Information);

            Application.Exit();
        }

        private void textBox1_KeyUp(object sender, KeyEventArgs e)
        {
        }

        private void textBox1_MouseUp(object sender, MouseEventArgs e)
        {
            try
            {
                textBox1.SelectionLength = 0;
            }
            catch { }
        }

        private void textBox1_KeyDown(object sender, KeyEventArgs e)
        {
            try
            {
                int pos = textBox1.SelectionStart;

                if ((e.KeyCode == Keys.Left || e.KeyCode == Keys.Right || e.KeyCode == Keys.Up || e.KeyCode == Keys.Down) && e.Shift)
                {
                    textBox1.SelectionStart = pos;
                    textBox1.SelectionLength = 0;
                }
                if (e.KeyCode == Keys.Back && pos > 0)
                {
                    var misprint = new Misprint()
                    {
                        Type = MisprintToString(LevenshteinMisprintType.Delete),
                        Symbols = new List<MisprintSymbol>() { new MisprintSymbol() { Symbol = textBox1.Text[pos - 1], Position = pos } }
                    };

                    _misprints.Add(misprint);
                    isLastKeyDelete = true;
                }
                if (e.KeyCode == Keys.Delete && pos < textBox1.Text.Length - 1)
                {
                    var misprint = new Misprint()
                    {
                        Type = MisprintToString(LevenshteinMisprintType.Delete),
                        Symbols = new List<MisprintSymbol>() { new MisprintSymbol() { Symbol = textBox1.Text[pos], Position = pos + 1 } }
                    };

                    _misprints.Add(misprint);
                    isLastKeyDelete = true;
                }
            }
            catch { }
        }

        private bool isLastKeyDelete = false;

        //private char lastInput = ' ';

        private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
        {
            try
            {
                int pos = textBox1.SelectionStart;

                //if (char.IsLetterOrDigit(e.KeyChar))
                {
                    if (_misprints.Count > 1 &&
                         isLastKeyDelete == false &&
                         _misprints[_misprints.Count - 2].Type == MisprintToString(LevenshteinMisprintType.Delete) &&
                         _misprints[_misprints.Count - 1].Type == MisprintToString(LevenshteinMisprintType.Substitute) &&
                         _misprints[_misprints.Count - 1].Symbols[0].Position == _misprints[_misprints.Count - 2].Symbols[0].Position - 1 &&
                         _misprints[_misprints.Count - 1].Symbols[0].Position == pos &&
                         _misprints[_misprints.Count - 1].Symbols[1].Symbol == _misprints[_misprints.Count - 2].Symbols[0].Symbol &&
                         _misprints[_misprints.Count - 1].Symbols[0].Symbol == e.KeyChar)
                    {
                        var misprint = new Misprint()
                        {
                            Type = "incorrect-order",
                            Symbols = new List<MisprintSymbol>()
                        };

                        misprint.Symbols.Insert(0, _misprints[_misprints.Count - 1].Symbols[0]);
                        misprint.Symbols.Insert(0, _misprints[_misprints.Count - 1].Symbols[1]);

                        _misprints.Add(misprint);
                        _misprints.RemoveAt(_misprints.Count - 2);
                        _misprints.RemoveAt(_misprints.Count - 2);
                    }
                    else if (_misprints.Count > 0 &&
                        isLastKeyDelete == true &&
                        _misprints[_misprints.Count - 1].Type == MisprintToString(LevenshteinMisprintType.Delete) &&
                        _misprints[_misprints.Count - 1].Symbols[0].Position == pos + 1)
                    {
                        var misprint = new Misprint()
                        {
                            Type = MisprintToString(LevenshteinMisprintType.Substitute),
                            Symbols = new List<MisprintSymbol> { new MisprintSymbol() { Symbol = e.KeyChar, Position = pos + 1 } }
                        };

                        misprint.Symbols.Insert(0, _misprints[_misprints.Count - 1].Symbols[0]);

                        _misprints.Add(misprint);
                        _misprints.RemoveAt(_misprints.Count - 2);
                    }
                    else if (pos != textBox1.Text.Length && KeyDistance.KeyExists(e.KeyChar))
                    {
                        var misprint = new Misprint()
                        {
                            Type = MisprintToString(LevenshteinMisprintType.Insert),
                            Symbols = new List<MisprintSymbol> { new MisprintSymbol() { Symbol = e.KeyChar, Position = pos } }
                        };

                        _misprints.Add(misprint);
                    }

                    if (KeyDistance.KeyExists(e.KeyChar))
                        isLastKeyDelete = false;
                }
            }
            catch { }
        }
         
        //static string _lastText = "  ";
        private void textBox1_TextChanged(object sender, EventArgs e)
        {
        }

        private string MisprintToString(LevenshteinMisprintType type)
        {
            switch (type)
            {
                case LevenshteinMisprintType.Delete:
                    return "extra-letter";
                case LevenshteinMisprintType.Insert:
                    return "missed-letter";
                case LevenshteinMisprintType.Substitute:
                    return "replace-letter";
            }

            return type.ToString();
        }

        private void изменитьПутьДляСохраненияОтчетовToolStripMenuItem_Click(object sender, EventArgs e)
        {
            try
            {
                if (folderBrowserDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    Program.pathLog = folderBrowserDialog.SelectedPath + "\\";
                    File.WriteAllText("MisprintTestingTool.set", Program.pathLog);
                }
            }
            catch { }
        }
    }
}
